package com.lei.study.cds.unit2;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mvel2.MVEL;
import org.mvel2.ParserContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * mvel计算工具类
 * 常用计算工具以及parserContext初始化
 *
 * @author 伍磊
 */
@Component
@Slf4j
public class MvelCalculateTools {

    /**
     * parserContext  存储java中的函数
     */
    public ParserContext parserContext = new ParserContext();

    /**
     * function  存储数据库表中自定义的函数
     */
    public String function;

    /**
     * 计算公式 - 编译后对象 map
     */
    public ThreadLocal<Map<String, Serializable>> MAP = new ThreadLocal<>();

    private static final String HASH_TAG = "#";

    private static final String DELIMITER = "T0SEP0";

    private static final String ADD = "A";

    private static final String REDUCE = "R";

    /**
     * parserContext初始化
     * @return
     */
    @PostConstruct
    public void init() {
        // 初始化parserContext
        try {
            parserContext.addImport("AVG", MvelCalculateTools.class.getMethod("avg", BigDecimal[].class));
            parserContext.addImport("MIN", MvelCalculateTools.class.getMethod("min", BigDecimal[].class));
            parserContext.addImport("MAX", MvelCalculateTools.class.getMethod("max", BigDecimal[].class));
            parserContext.addImport("MID", MvelCalculateTools.class.getMethod("mid", BigDecimal[].class));
            parserContext.addImport("COUNT", MvelCalculateTools.class.getMethod("count", BigDecimal[].class));
            parserContext.addImport("SUM", MvelCalculateTools.class.getMethod("sum", BigDecimal[].class));
            parserContext.addImport("STDEV", MvelCalculateTools.class.getMethod("stdev", BigDecimal[].class));
        } catch (Exception e) {
            System.out.println("异常");
            throw new RuntimeException("异常");
        }
        System.out.println("parser init...");

        // 初始化定义函数 todo 从表中读取
        String importPackage = "import java.lang.Math;  import java.math.BigDecimal;";
        String content = "def defMin(a, b) {return new BigDecimal(Math.min(a, b) + '');}";
        function = importPackage + content;
        System.out.println("function init..." + function);
    }

    /**
     * 解析参数
     * 带有时间的参数会返回对应的偏移量
     * @param params
     * @return
     */
    public List<MvelCalculateParamParseVO> parseParams(List<String> params) {
        List<MvelCalculateParamParseVO> returnList = new ArrayList<>();
        params.forEach(param -> {
            MvelCalculateParamParseVO mvelCalculateParamParseVO = new MvelCalculateParamParseVO();
            mvelCalculateParamParseVO.setParam(param);
            // 带有时间参数解析
            if (param.contains(DELIMITER)) {
                String time = StringUtils.substringBefore(param, DELIMITER);
                int offset;
                if (time.startsWith(ADD)) {
                    // 偏移量为正
                    offset = Integer.parseInt(StringUtils.substringAfter(time, ADD));

                } else {
                    // 偏移量为负
                    offset = -Integer.parseInt(StringUtils.substringAfter(time, ADD));

                }
                mvelCalculateParamParseVO.setOffset(offset);
            }
            returnList.add(mvelCalculateParamParseVO);
        });

        return returnList;
    }


    /**
     * 计算
     * @param express
     * @param paramsMap
     * @return
     */
    public Object calculate(String express, Map<String, BigDecimal> paramsMap) {
        Serializable compiled = MAP.get().get(express);
        if (null == compiled) {
            compiled = MVEL.compileExpression(express, parserContext);
            MAP.get().put(express, compiled);
        }

        return MVEL.executeExpression(compiled, paramsMap);
    }


    /**
     * 解析
     * @param mvelCalculateParseDTO
     * @return
     */
    public MvelCalculateParseVO parse(MvelCalculateParseDTO mvelCalculateParseDTO) {
        MvelCalculateParseVO mvelCalculateParseVO = new MvelCalculateParseVO();

        // 1 校验数据标签是否存在 todo
        List<String> projectCodeList = mvelCalculateParseDTO.getProjectCodeList();

        // 判断数据标签是否都为基础项目 或能找到衍生项目对应的完全解析公式 todo 规则时无需考虑

        // 2 校验函数是否存在 todo
        List<String> functionList = mvelCalculateParseDTO.getFunctionList();

        // 3 特殊词替换-1 查询符号替换表中TYPE=10的数据 根据查询的数据进行替换
        String express = mvelCalculateParseDTO.getExpress();

        // 4 处理公式使用标签
        List<String> wholeParams = handlerParam(projectCodeList, express);

        // 5 特殊词替换-2 查询符号替换表中TYPE=20的数据 根据查询出的数据进行替换，主要包含2部分替换
        // 5.1  衍生计算公式/规则的替换

        // 5.2 上一步得到的实际参数的替换

        // 6 处理能够处理衍生项目

        // 7 演算
        // 7.1 mock 数据
        Map<String, Object> map = new HashMap<>();
        wholeParams.forEach(param -> map.put(param, new BigDecimal("0.01")));

        // 7.2 计算
        Serializable compiled = MAP.get().get(express);
        if (null == compiled) {
            compiled = MVEL.compileExpression(express, parserContext);
        }
        try {
            Object obj = MVEL.executeExpression(compiled, map);
        } catch (Exception e) {
            log.error("计算公式 {} 演算时出现异常 {}", mvelCalculateParseDTO.getExpress(), e.getMessage());
            mvelCalculateParseVO.setFlag(false);
            mvelCalculateParseVO.setErrorMessage(e.getMessage());
            return mvelCalculateParseVO;
        }

        // 8 演算通过 组装返回值
        mvelCalculateParseVO.setFlag(true);
        // todo
        mvelCalculateParseVO.setParseFlag(false);
        mvelCalculateParseVO.setParseExpress(express);
        mvelCalculateParseVO.setParseLabel(StringUtils.join(projectCodeList, "|"));
        mvelCalculateParseVO.setParseFunction(StringUtils.join(functionList, "|"));
        mvelCalculateParseVO.setParseParams(StringUtils.join(wholeParams, "|"));

        return mvelCalculateParseVO;
    }

    /**
     * 计算平均数
     * @param values
     * @return
     */
    public static BigDecimal avg(BigDecimal... values) {
        int length = values.length;
        BigDecimal sum = BigDecimal.ZERO;
        for (BigDecimal value : values) {
            sum = sum.add(value);
        }
        return sum.divide(new BigDecimal(length),8, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 计算最小值
     * @param values
     * @return
     */
    public static BigDecimal min(BigDecimal... values) {
        BigDecimal min = values[0];
        for (BigDecimal value : values) {
            if (min.compareTo(value) > 0) {
                min = value;
            }
        }
        return min;
    }

    /**
     * 计算最大值
     * @param values
     * @return
     */
    public static BigDecimal max(BigDecimal... values) {
        BigDecimal max = BigDecimal.ZERO;
        for (BigDecimal value : values) {
            if (value.compareTo(max) > 0) {
                max = value;
            }
        }
        return max;
    }

    /**
     * 计算中位数
     * @param values
     * @return
     */
    public static BigDecimal mid(BigDecimal... values) {
        List<BigDecimal> list = Arrays.asList(values);
        Collections.sort(list);

        BigDecimal mid;
        int size = list.size();
        if (size % 2 == 0) {
            mid = (list.get(size / 2 - 1).add(list.get(size / 2))).divide(new BigDecimal(2),8, BigDecimal.ROUND_HALF_UP);
        } else {
            mid = list.get(size / 2);
        }
        return mid;
    }

    /**
     * 计算总个数
     * @param values
     * @return
     */
    public static int count(BigDecimal... values) {
        return values.length;
    }

    /**
     * 计算总和
     * @param values
     * @return
     */
    public static BigDecimal sum(BigDecimal... values) {
        BigDecimal sum = BigDecimal.ZERO;
        for (BigDecimal value : values) {
            sum = sum.add(value);
        }
        return sum;
    }

    /**
     * 计算标准差
     * @param values
     * @return
     */
    public static BigDecimal stdev(BigDecimal... values) {
        int length = values.length;

        // 求和
        BigDecimal sum = BigDecimal.ZERO;
        for (BigDecimal value : values) {
            sum = sum.add(value);
        }

        // 求平均数
        BigDecimal average = sum.divide(new BigDecimal(length), 8, BigDecimal.ROUND_HALF_UP);

        // 求方差
        BigDecimal total = BigDecimal.ZERO;
        for(int i = 0; i < length; i++) {
            total = total.add((values[i].subtract(average)).multiply(values[i].subtract(average)));
        }

        // 求标准差
        double sqrt = Math.sqrt(total.divide(new BigDecimal(length), 8, BigDecimal.ROUND_HALF_UP).doubleValue());
        return new BigDecimal(Double.toString(sqrt));
    }


    /**
     * 处理公式使用标签
     * @param params
     * @param express
     * @return
     */
    private List<String> handlerParam(List<String> params, String express) {
        List<String> newParams = new ArrayList<>();
        params.forEach(param -> {
            String old = express;
            String newParam = HASH_TAG + param;
            while (old.contains(newParam)) {
                String prefix = StringUtils.substringAfterLast(StringUtils.substringBefore(old, newParam), HASH_TAG);
                newParams.add(HASH_TAG + prefix + newParam);
                old = StringUtils.substringAfter(old, newParam);
            }
            if (old.contains(param)) {
                newParams.add(param);
            }
        });
        return newParams;
    }

    /**
     * parserContext初始化
     * @return
     */
    public static ParserContext initParse() {
        ParserContext parserContext = new ParserContext();
        try {
            parserContext.addImport("AVG", MvelCalculateTools.class.getMethod("avg", BigDecimal[].class));
            parserContext.addImport("MIN", MvelCalculateTools.class.getMethod("min", BigDecimal[].class));
            parserContext.addImport("MAX", MvelCalculateTools.class.getMethod("max", BigDecimal[].class));
            parserContext.addImport("MID", MvelCalculateTools.class.getMethod("mid", BigDecimal[].class));
            parserContext.addImport("COUNT", MvelCalculateTools.class.getMethod("count", BigDecimal[].class));
            parserContext.addImport("SUM", MvelCalculateTools.class.getMethod("sum", BigDecimal[].class));
            parserContext.addImport("STDEV", MvelCalculateTools.class.getMethod("stdev", BigDecimal[].class));
        } catch (Exception e) {
            System.out.println("异常");
            throw new RuntimeException("异常");
        }
        return parserContext;
    }
}
